home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 21 code / Hierarchical Lists / Libs / CTwistDownListBox.cp < prev    next >
Encoding:
Text File  |  1994-09-15  |  11.4 KB  |  355 lines  |  [TEXT/MMCC]

  1. // ===========================================================================
  2. //    CTwistDownListBox.h            ©1994 Jan Bruyndonckx All rights reserved.
  3. //    v1.0 18/6/94
  4. //  Inspired by M.Minow's article in Develop 18
  5. //
  6. //    A hierarchical listbox subclass.
  7. //  The default listbox contains only string data.
  8. // ===========================================================================
  9.  
  10. #include <types.h>
  11. #include <strings.h>
  12. #include <string.h>
  13. #include <Palettes.h>
  14. #include "CTwistDownListBox.h"
  15.  
  16. //----------------------------------------------------------------------------
  17. // The trianges
  18.  
  19. PolyHandle    CTwistDownListBox::sClosedPoly = NULL,
  20.               CTwistDownListBox::sOpenedPoly = NULL,
  21.               CTwistDownListBox::sIntermediatePoly = NULL ;
  22.  
  23. static void DrawTriangle (PolyHandle aPoly, const Boolean drawFilled,
  24.                           const short posV, const short posH) ;
  25.  
  26. // These macros simplify access to the flag word in the list element.
  27. #define SetTDFlag(flags, mask)        ((flags) |= (mask))
  28. #define ClearTDFlag(flags, mask)    ((flags) &= ~(mask))
  29. #define InvertTDFlag(flags, mask)    ((flags) ^= (mask))
  30. #define TestTDFlag(flags, mask)        (((flags) & (mask)) != 0)
  31.  
  32. typedef enum {                        // Misc. constants
  33.     kTriangleOutsideGap    = 1,            // From margin to button
  34.     kTriangleInsideGap    = 3,            // From button to text
  35.     kScrollBarWidth        = 16,            // width of right scroll bar
  36.     kAnimationDelay        = 3,            // the time to show the intermediate button
  37.     kIndentOffset        = 16            // to offset each indentation level by
  38. } MiscConstants ;
  39.  
  40. //----------------------------------------------------------------------------
  41. // Creation methods
  42.  
  43. CTwistDownListBox* CTwistDownListBox::CreateFromStream(LStream *inStream)
  44. {
  45.   return (new CTwistDownListBox(inStream));
  46. }
  47.  
  48. CTwistDownListBox::CTwistDownListBox() : CCustomListBox()
  49. {
  50.   init () ;
  51. }
  52.  
  53. CTwistDownListBox::CTwistDownListBox(const SPaneInfo &inPaneInfo,
  54.                         Boolean inHasHorizScroll, Boolean inHasVertScroll,
  55.                         Boolean inHasGrow, Boolean inHasFocusBox,
  56.                         MessageT inDoubleClickMessage, Int16 inTextTraitsID,
  57.                         LCommander *inSuper) :
  58.                       CCustomListBox (inPaneInfo, inHasHorizScroll, inHasVertScroll,
  59.                                       inHasGrow, inHasFocusBox, inDoubleClickMessage, inTextTraitsID,
  60.                                       inSuper)
  61. {
  62.   init () ;
  63. }
  64.  
  65. CTwistDownListBox::CTwistDownListBox(LStream *inStream) : CCustomListBox (inStream)
  66. {
  67.   init () ;
  68. }
  69.  
  70. //----------------------------------------------------------------------------
  71. // Create the triangle buttons, if they didn't exist already
  72.  
  73. void CTwistDownListBox::init (void)
  74. {
  75.  // Create the twist down buttons
  76.  // Note that the port and text drawing characteristics must have been set.
  77.  
  78.   short        buttonSize;
  79.   short        halfSize;
  80.   short        intermediateSize;
  81.   FontInfo    info;
  82.  
  83.   ::GetFontInfo(&info);
  84.   buttonSize = info.ascent;                // The "show sublist" button
  85.   buttonSize &= ~1;                        // Round down to an even number
  86.   halfSize = buttonSize / 2;
  87.   intermediateSize = (buttonSize * 3) / 4;
  88.  
  89.   // note, we allocate the polygons once (they are static variables) for *all* lists, and
  90.   // we never dispose them
  91.  
  92.   if (sClosedPoly == NULL)
  93.       { sClosedPoly = ::OpenPoly();
  94.       ::MoveTo(halfSize, 0);
  95.       ::LineTo(buttonSize, halfSize);
  96.       ::LineTo(halfSize, buttonSize);
  97.       ::LineTo(halfSize, 0);
  98.       ::ClosePoly();
  99.       }
  100.   if (sOpenedPoly == NULL)
  101.       { sOpenedPoly = ::OpenPoly();
  102.       ::MoveTo(0, halfSize);
  103.       ::LineTo(buttonSize, halfSize);
  104.       ::LineTo(halfSize, buttonSize);
  105.       ::LineTo(0, halfSize);
  106.       ::ClosePoly();
  107.       ::OffsetPoly (sOpenedPoly, 1, -2) ;
  108.       }
  109.   if (sIntermediatePoly == NULL)
  110.       { sIntermediatePoly = ::OpenPoly();    
  111.       ::MoveTo(intermediateSize, 0);
  112.       ::LineTo(intermediateSize, intermediateSize);
  113.       ::LineTo(0, intermediateSize);
  114.       ::LineTo(intermediateSize, 0);
  115.       ::ClosePoly();
  116.       }
  117.   
  118.   // Remember the width of the "button" area.
  119.   triangleWidth = (**sOpenedPoly).polyBBox.right
  120.                     + kTriangleOutsideGap
  121.                     + kTriangleInsideGap;
  122. }
  123.  
  124. //----------------------------------------------------------------------------
  125. // If the data-field contains text (default case), then get/set it
  126.  
  127. StringPtr CTwistDownListBox::GetDescriptor(Str255 outDescriptor) const
  128. { Byte            buffer[260] ;
  129.   short            l = sizeof (buffer) ;
  130.   TwistDownRecPtr twistElement = (TwistDownRecPtr) buffer ;
  131.   
  132.   twistElement->data[0] = '\0' ;
  133.   GetSelection (twistElement, &l) ;
  134.   l -= TwistDownRecSize ;
  135.   ::memcpy (outDescriptor+1, twistElement->data, l) ;
  136.   outDescriptor[0] = l ;
  137.   
  138.   return outDescriptor ;
  139. }
  140.  
  141. void CTwistDownListBox::SetDescriptor(ConstStr255Param inDescriptor)
  142. { Byte            buffer[260] ;
  143.   short            l ;
  144.   TwistDownRecPtr twistElement = (TwistDownRecPtr) buffer ;
  145.   
  146.   l = *inDescriptor+1 ;
  147.   ::memcpy (twistElement->data, inDescriptor+1,  l) ;
  148.   twistElement->indent = 0 ;
  149.   twistElement->flags = 0 ;
  150.   l += TwistDownRecSize ;
  151.   SetSelection (twistElement, l) ;
  152. }
  153.  
  154. //----------------------------------------------------------------------------
  155. // The user clicked in the list
  156.  
  157. void CTwistDownListBox::ClickSelf(const SMouseDownEvent &inMouseDown)
  158. { long    l ;
  159.  
  160.   // set target to the right pane
  161.   SwitchTarget(this);
  162.   FocusDraw();
  163.  
  164.   // is the mousedown in the twistDown button area?
  165.   Point mousePt = inMouseDown.macEvent.where ;
  166.   ::GlobalToLocal (&mousePt) ;
  167.   Rect hitRect = (*mMacListH)->rView ;
  168.   hitRect.right += kScrollBarWidth ;
  169.   if (::PtInRect (mousePt, &hitRect) == false)        // click is not in our pane
  170.       return ;
  171.   hitRect.right = (*mMacListH)->rView.left + triangleWidth ;
  172.   if (::PtInRect (mousePt, &hitRect))                    // yes, is it in a button?
  173.     { short visibleTop = (*mMacListH)->visible.top ;
  174.       short cellHeight = (*mMacListH)->cellSize.v ;
  175.       Cell theCell ;                                // the selected cell
  176.       theCell.v = (mousePt.v - hitRect.top) / cellHeight + visibleTop ;
  177.       theCell.h = 0 ;
  178.          TwistDownRecPtr    twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  179.  
  180.          if ((twist == NULL) || (TestTDFlag(twist->flags, kHasSubList) == 0))
  181.              goto end ;
  182.  
  183.       SetTDFlag (twist->flags, kDrawFilled) ;                    // Draw the button
  184.          ::LDraw (theCell, mMacListH) ;                            // Draw the cell anew
  185.          hitRect.top += ((theCell.v - visibleTop) * cellHeight) ;    // set to the dimensions of the button
  186.          hitRect.bottom = hitRect.top + cellHeight ;
  187.          Boolean inHitRect = false ;
  188.          if (::StillDown())
  189.            { inHitRect = true ;
  190.              while (::WaitMouseUp())    
  191.             { ::GetMouse (&mousePt);
  192.               Boolean newInHitRect = ::PtInRect(mousePt, &hitRect) ;
  193.               if (newInHitRect != inHitRect)
  194.                 { twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  195.                   InvertTDFlag (twist->flags, kDrawFilled) ;
  196.                   ::LDraw (theCell, mMacListH) ;
  197.                   inHitRect = newInHitRect ;
  198.                 }
  199.             }
  200.         }
  201.       
  202.          twist = (TwistDownRecPtr) GetCellPtr (theCell) ;            // the user released the mouse
  203.          if (inHitRect == false) 
  204.              { // make very sure the button is cleared
  205.                if (TestTDFlag (twist->flags, kDrawFilled))
  206.                  { ClearTDFlag (twist->flags, kDrawFilled) ;
  207.                    ::LDraw (theCell, mMacListH) ;
  208.                  }
  209.                return ;        // and get out…
  210.              }
  211.          
  212.          SetTDFlag (twist->flags, kDrawIntermediate) ;                // Draw the animation triangle
  213.          ::LDraw (theCell, mMacListH) ;
  214.          ::Delay (kAnimationDelay, &l) ;
  215.          twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  216.          ClearTDFlag (twist->flags, kDrawIntermediate) ;
  217.          
  218.          ::LSetDrawingMode (false, mMacListH) ;                    // here send message… 
  219.          if (TestTDFlag (twist->flags, kIsOpened))                    // …to expand/collapse sublist
  220.              { ClearTDFlag (twist->flags, kIsOpened) ;
  221.                CollapseElement (theCell) ;
  222.              }
  223.          else
  224.              { SetTDFlag (twist->flags, kIsOpened) ;
  225.                ExpandElement (theCell) ;
  226.              }
  227.          ::LSetDrawingMode (true, mMacListH) ;                        // redraw list again…
  228.          twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  229.          ClearTDFlag (twist->flags, kDrawFilled) ;
  230.          hitRect = (*mMacListH)->rView ;
  231.          ResizeFrameBy (0, 0, true) ;    // this works more correctly when collapsing & rows are 'above' the top
  232.          return ;
  233.     }
  234.   
  235. end:      // mousedown was not in a button…
  236.   inherited::ClickSelf (inMouseDown) ;
  237. }
  238.  
  239. //----------------------------------------------------------------------------
  240.  
  241. void CTwistDownListBox::DrawElementSelf (const Rect *lRect, 
  242.                                          const void *lElement, 
  243.                                          const short lDataLen)
  244.                                          
  245. // Draw a single list cell on the screen.
  246. // Checks flags and draws triangular button if needed;
  247. // calls DrawTwistedElement to draw cell contents.
  248.  
  249.   TwistDownRecPtr twistElement = (TwistDownRecPtr) lElement ;
  250.  
  251.   if (TestTDFlag (twistElement->flags, kHasSubList))
  252.       { PolyHandle    aPoly = NULL ;
  253.         
  254.         aPoly = TestTDFlag(twistElement->flags, kIsOpened) ? sOpenedPoly : sClosedPoly ;
  255.         if (TestTDFlag(twistElement->flags, kDrawIntermediate))
  256.             aPoly = sIntermediatePoly ;
  257.         if (aPoly)
  258.             DrawTriangle (aPoly, 
  259.                           TestTDFlag(twistElement->flags, kDrawFilled),
  260.                             lRect->top+1, 
  261.                             lRect->left+kTriangleOutsideGap) ;
  262.       }
  263.  
  264.   ::MoveTo (lRect->left + triangleWidth + 2 + twistElement->indent*kIndentOffset,
  265.             lRect->top + 10) ;
  266.   DrawTwistedElement (lRect, twistElement, lDataLen) ;
  267. }
  268.  
  269. //----------------------------------------------------------------------------
  270.  
  271. void CTwistDownListBox::DrawTwistedElement (const Rect *lRect, 
  272.                                             const TwistDownRecPtr twistElement, 
  273.                                             const short lDataLen)
  274.  
  275. // Draw contents of a single list element.
  276. // Default version just draws text; override for other types of data.
  277.  
  278. {
  279.   ::DrawText (twistElement->data, 0, lDataLen-TwistDownRecSize) ;
  280. }
  281.  
  282. //----------------------------------------------------------------------------
  283. // User closed a sublist.  Remove all sublists hanging from this element.
  284. // Override in case an element contains a pointer to some allocated memory.
  285.  
  286. void CTwistDownListBox::CollapseElement (const Cell theCell)
  287. { short                count = 0 ;
  288.   TwistDownRecPtr    twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  289.   short                headIndent = twist->indent ;
  290.   Cell                cell = theCell ;
  291.   
  292.   for (cell.v++ ; ; cell.v++)
  293.     { twist = (TwistDownRecPtr) GetCellPtr (cell) ;
  294.       if (twist == NULL)
  295.           break ;
  296.       if (twist->indent <= headIndent)
  297.           break ;
  298.       count++ ;
  299.     }
  300.    
  301.   if (count)
  302.        ::LDelRow (count, theCell.v+1, mMacListH) ;
  303. }
  304.  
  305. //----------------------------------------------------------------------------
  306. // You'd better override me!
  307.  
  308. void CTwistDownListBox::ExpandElement (const Cell theCell)
  309. {
  310. }
  311.  
  312. //----------------------------------------------------------------------------
  313.  
  314. static void DrawTriangle (PolyHandle aPoly, const Boolean drawFilled,
  315.                           const short posV, const short posH)
  316. { const short kGrayness = 4 ;            // 1 for intermediate gray, 8 for very light gray
  317.  
  318.   ::OffsetPoly (aPoly, posH, posV);        // move poly to the right spot
  319.  
  320.   if (drawFilled)
  321.      ::FillPoly(aPoly, &qd.black);
  322.   else
  323.    { RGBColor foreColor;
  324.      RGBColor saveForeColor;
  325.      RGBColor backColor;
  326.      
  327.      ::GetForeColor(&foreColor);
  328.      ::GetBackColor(&backColor);
  329.      saveForeColor = foreColor;
  330.  
  331.      // This loop sets foreColor to a very light gray.
  332.      for (short i = 0; i < kGrayness; i++)
  333.        if (::GetGray(GetGDevice(), &backColor, &foreColor) == false)
  334.             break;
  335.  
  336.      if ((foreColor.red   == 0) &&
  337.           (foreColor.green == 0) &&
  338.           (foreColor.blue  == 0))
  339.        ::ErasePoly (aPoly) ;
  340.      else
  341.       { ::RGBForeColor(&foreColor);
  342.         ::FillPoly(aPoly, &qd.black);
  343.         ::RGBForeColor(&saveForeColor);
  344.       }
  345.  
  346.      ::FramePoly (aPoly) ;
  347.    }
  348.    
  349.   ::OffsetPoly (aPoly, -posH, -posV);
  350. }
  351.  
  352. //----------------------------------------------------------------------------
  353.  
  354.